<%#
 Copyright (c) 2025 Gxxkx
 Licensed to the public under the MIT license.
-%>

<%+header%>

<h2 name="content"><%:Host Updater%></h2>

<div id="hostupdater-container" class="hu-wrap">
	<div class="cbi-section hu-card">
		<h3><%:Basic Settings%></h3>
		<div class="cbi-section-descr"><%:Manage multiple hosts subscription sources with scheduled and manual fetching%></div>
		<div class="hu-grid2">
			<div class="cbi-value">
				<label class="cbi-value-title"><%:Enable Service%></label>
				<div class="cbi-value-field">
					<input type="checkbox" id="enabled" />
					<label for="enabled"><%:Enable automatic update service%></label>
				</div>
			</div>
			<div class="cbi-value">
				<label class="cbi-value-title"><%:Update Interval%></label>
				<div class="cbi-value-field">
					<select id="interval">
						<option value="1" selected><%:1 hour%></option>
						<option value="2"><%:2 hours%></option>
						<option value="3"><%:3 hours%></option>
						<option value="6"><%:6 hours%></option>
						<option value="12"><%:12 hours%></option>
						<option value="24"><%:24 hours%></option>
					</select>
				</div>
			</div>
		</div>
		<div class="hu-actions">
			<button class="btn cbi-button-save" onclick="hostupdater.saveConfig()"><%:Save Configuration%></button>
		</div>
	</div>

	<div class="cbi-section hu-card">
		<h3><%:Subscription Sources Management%></h3>
		<div class="cbi-section-descr"><%:Add, delete, enable, disable subscription sources, support manual update for individual sources%></div>
		<div class="hu-grid2 hu-gap">
			<div class="cbi-value">
				<label class="cbi-value-title"><%:Source Name%></label>
				<div class="cbi-value-field"><input type="text" id="new-source-name" placeholder="<%:Name%>" /></div>
			</div>
			<div class="cbi-value">
				<label class="cbi-value-title"><%:Source URL%></label>
				<div class="cbi-value-field"><input type="text" id="new-source-url" placeholder="<%:URL%>" /></div>
			</div>
		</div>
		<div class="hu-actions">
			<button class="btn cbi-button-add" onclick="hostupdater.addSource()"><%:Add%></button>
		</div>
		<div class="hu-table-wrap">
			<div id="sources-list"><%:Loading...%></div>
		</div>
	</div>

	<div class="cbi-section hu-card">
		<h3><%:Batch Operations%></h3>
		<div class="hu-actions">
			<button class="btn cbi-button-apply" onclick="hostupdater.updateAll()"><%:Update All%></button>
			<button class="btn cbi-button-remove" onclick="hostupdater.performRestore()"><%:Restore Hosts%></button>
			<!-- <button class="btn cbi-button-reload" onclick="hostupdater.updateStatus()"><%:Refresh Status%></button> -->
		</div>
	</div>

	<div class="cbi-section hu-card">
		<h3><%:Current /etc/hosts Content%></h3>
		<div class="hu-row">
			<button class="btn cbi-button-reload" onclick="hostupdater.loadHosts()"><%:Refresh%></button>
		</div>
		<pre id="hosts-display" class="log-content" style="min-height:220px"><%:Click "Refresh" button to load current /etc/hosts%></pre>
	</div>

	<div class="cbi-section hu-card">
		<h3><%:Log Viewer%></h3>
		<div class="hu-row">
			<select id="log-lines">
				<option value="50">50</option>
				<option value="100" selected>100</option>
				<option value="200">200</option>
				<option value="500">500</option>
			</select>
			<button class="btn cbi-button-reload" onclick="hostupdater.viewLog()"><%:View Log%></button>
			<button class="btn cbi-button-remove" onclick="hostupdater.clearLog()"><%:Clear Log%></button>
		</div>
		<pre id="log-display" class="log-content"><%:Click "View Log" button to display logs%></pre>
	</div>
</div>

<script type="text/javascript">
'use strict';
(function(){
	function esc(s){return (s||'').replace(/[&<>"']/g, m=>({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[m]));}
	var hostupdater = {
		init(){ this.loadConfig(); this.loadSources(); this.loadHosts(); },
		msg(t,m,ok){ var c=document.getElementById('hostupdater-container'); var el=document.createElement('div'); el.className='alert '+(ok?'alert-success':'alert-danger'); el.innerHTML='<strong>'+esc(t)+'</strong>'+(m?('<br>'+esc(m)):''); c.insertAdjacentElement('afterbegin',el); setTimeout(()=>{el.remove();},2500); },
		loadConfig(){ fetch('/cgi-bin/luci/admin/services/hostupdater/config').then(r=>r.json()).then(d=>{ document.getElementById('enabled').checked=!!d.enabled; if(d.interval) document.getElementById('interval').value=d.interval; }).catch(()=>{}); },
		saveConfig(){ const p={enabled:document.getElementById('enabled').checked, interval:document.getElementById('interval').value}; fetch('/cgi-bin/luci/admin/services/hostupdater/config',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(p)}).then(r=>r.json()).then(d=>{ this.msg('<%:Save Configuration%>',d.success?'<%:Success%>':'<%:Failed%>',d.success); }).catch(e=>this.msg('<%:Save Configuration Failed%>',e.message,false)); },
		loadSources(){ fetch('/cgi-bin/luci/admin/services/hostupdater/sources').then(r=>r.json()).then(d=>this.renderSources(d||[])).catch(()=>this.renderSources([])); },
		renderSources(list){ const wrap=document.getElementById('sources-list'); if(!list.length){ wrap.innerHTML='<p><%:No subscription sources%></p>'; return;} let html='<table class="table hu-table"><thead><tr><th><%:Name%></th><th><%:URL%></th><th><%:Status%></th><th class="hu-ops"><%:Operations%></th></tr></thead><tbody>'; list.forEach(s=>{ html+= '<tr>'+'<td>'+esc(s.name)+'</td>'+'<td class="hu-url">'+esc(s.url)+'</td>'+'<td>'+(s.enabled?'<span class="status-enabled"><%:Enabled%></span>':'<span class="status-disabled"><%:Disabled%></span>')+'</td>'+'<td class="hu-ops">'+'<button class="btn btn-mini" onclick="hostupdater.toggle(\''+encodeURIComponent(s.name)+'\')">'+(s.enabled?'<%:Disable%>':'<%:Enable%>')+'</button> '+'<button class="btn btn-mini" onclick="hostupdater.updateOne(\''+encodeURIComponent(s.name)+'\')"><%:Update%></button> '+'<button class="btn btn-mini btn-danger" onclick="hostupdater.remove(\''+encodeURIComponent(s.name)+'\')"><%:Delete%></button>'+'</td>'+'</tr>'; }); html+='</tbody></table>'; wrap.innerHTML=html; },
		addSource(){ const name=document.getElementById('new-source-name').value.trim(); const url=document.getElementById('new-source-url').value.trim(); if(!name||!url) return this.msg('<%:Add Failed%>','<%:Please fill in name and URL%>',false); const p={name,url,enabled:true}; fetch('/cgi-bin/luci/admin/services/hostupdater/sources',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(p)}).then(r=>r.json()).then(d=>{ this.msg('<%:Add Subscription Source%>',d.success?'<%:Success%>':'<%:Failed%>',d.success); if(d.success){ this.loadSources(); document.getElementById('new-source-name').value=''; document.getElementById('new-source-url').value=''; }}).catch(e=>this.msg('<%:Add Failed%>',e.message,false)); },
		toggle(nameEnc){ const name=decodeURIComponent(nameEnc); const form=new URLSearchParams(); form.set('name',name); fetch('/cgi-bin/luci/admin/services/hostupdater/source/toggle',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:form.toString()}).then(r=>r.json()).then(d=>{ this.msg('<%:Toggle Status%>',d.success?'<%:Success%>':'<%:Failed%>',d.success); if(d.success) this.loadSources(); }).catch(e=>this.msg('<%:Toggle Failed%>',e.message,false)); },
		updateOne(nameEnc){ const name=decodeURIComponent(nameEnc); fetch('/cgi-bin/luci/admin/services/hostupdater/source/update?name='+encodeURIComponent(name)).then(r=>r.text()).then(()=>{ this.msg('<%:Single Source Update%>','<%:Completed%>',true); this.updateStatus(); }).catch(e=>this.msg('<%:Single Source Update Failed%>',e.message,false)); },
		remove(nameEnc){ const name=decodeURIComponent(nameEnc); if(!confirm('<%:Confirm delete subscription source%>: '+name+' ?')) return; const form=new URLSearchParams(); form.set('name',name); fetch('/cgi-bin/luci/admin/services/hostupdater/source/delete',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:form.toString()}).then(r=>r.json()).then(d=>{ this.msg('<%:Delete Subscription Source%>',d.success?'<%:Success%>':'<%:Failed%>',d.success); if(d.success) this.loadSources(); }).catch(e=>this.msg('<%:Delete Failed%>',e.message,false)); },
		updateAll(){ fetch('/cgi-bin/luci/admin/services/hostupdater/update').then(r=>r.text()).then(()=>{ this.msg('<%:Update All%>','<%:Completed%>',true); this.updateStatus(); }).catch(e=>this.msg('<%:Update Failed%>',e.message,false)); },
		performRestore(){ if(!confirm('<%:Confirm restore /etc/hosts to original backup%>?')) return; fetch('/cgi-bin/luci/admin/services/hostupdater/restore').then(r=>r.text()).then(()=>{ this.msg('<%:Restore Completed%>','',true); this.updateStatus(); }).catch(e=>this.msg('<%:Restore Failed%>',e.message,false)); },
		updateStatus(){ /* 兼容留空 */ },
		loadHosts(){ fetch('/cgi-bin/luci/admin/services/hostupdater/hosts').then(r=>r.text()).then(t=>{ document.getElementById('hosts-display').textContent=t; }).catch(e=>this.msg('<%:Load hosts failed%>',e.message,false)); },
		viewLog(){ const n=document.getElementById('log-lines').value; fetch('/cgi-bin/luci/admin/services/hostupdater/log?lines='+encodeURIComponent(n)).then(r=>r.text()).then(t=>{ document.getElementById('log-display').textContent=t; }).catch(e=>this.msg('<%:View log failed%>',e.message,false)); },
		clearLog(){ if(!confirm('<%:Confirm clear log%>?')) return; const form=new URLSearchParams(); form.set('op','delete'); fetch('/cgi-bin/luci/admin/services/hostupdater/log',{method:'POST',headers:{'Content-Type':'application/x-www-form-urlencoded'},body:form.toString()}).then(r=>r.json()).then(d=>{ this.msg('<%:Clear Log%>',d.success?'<%:Success%>':'<%:Failed%>',d.success); if(d.success) document.getElementById('log-display').textContent=''; }).catch(e=>this.msg('<%:Clear log failed%>',e.message,false)); }
	};
	window.hostupdater=hostupdater;
	document.addEventListener('DOMContentLoaded',()=>hostupdater.init());
})();
</script>

<style>
.hu-wrap { display:flex; flex-direction:column; gap:16px; }
.hu-card { background:#fff; border:1px solid #ddd; border-radius:6px; padding:12px 14px; }
.hu-grid2 { display:flex; flex-direction: column; gap:12px 18px; align-items:center; }
.hu-gap { margin-top:8px; }
.hu-actions { margin-top:10px; display:flex; gap:8px; justify-content: center;}
.hu-table-wrap { margin-top:10px; overflow:auto; }
.hu-table { text-align: center; width:100%; border-collapse:collapse; }
.hu-table th, .hu-table td { padding:8px 10px; border-bottom:1px solid #e6e6e6; vertical-align:middle; }
.hu-table thead th { background:#f5f7fa; font-weight:600; color:#333; }
.hu-ops { white-space:nowrap; text-align:right; }
.hu-url { max-width:520px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.hu-row { display:flex; align-items:center; gap:10px;justify-content: flex-end; }
.hu-status { background:#f9fafb; border:1px solid #e5e7eb; border-radius:6px; padding:10px; }
.hu-status-list { list-style:none; padding:0; margin:0; display:grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap:6px 12px; }
.status-enabled { color:#15803d; font-weight:600; }
.status-disabled { color:#b91c1c; font-weight:600; }
.log-content { margin:10px; background:#0b1020; color:#e5e7eb; border:1px solid #111827; border-radius:6px; padding:12px; min-height:140px; max-height:420px; overflow:auto; }
.btn-mini { padding:2px 6px; font-size:11px; margin-right:6px; }
.btn-danger { background:#dc3545; border:#dc3545; color:#fff; }
.alert { padding:10px; margin:10px 0; border-radius:4px; }
.alert-success { background:#d4edda; border:1px solid #c3e6cb; color:#155724; }
.alert-danger { background:#f8d7da; border:1px solid #f5c6cb; color:#721c24; }
#new-source-name, #new-source-url { width:100%; box-sizing:border-box; }
</style>

<%+footer%> 